<template>
  <view style="width: 100%" class="mxp-upload-media">
    <mxp-grid :col="col" :xSpace="xSpace" :ySpace="ySpace" :itemHeight="itemHeight">
      <mxp-grid-item v-for="(item, index) in fileList" :key="index" :index="index">
        <view style="position: relative; width: 100%; height: 100%; overflow: hidden" class="item-border-radius">
          <view class="item-image-box" @click="preview(index)">
            <image v-if="item.type === 'image'" key="image-1" class="img" mode="aspectFill" :style="{ width: '100%', height: '100%' }" :src="item.path"></image>
            <image
              v-else-if="item.type === 'video' && item.poster"
              key="image-2"
              mode="aspectFill"
              class="thumb"
              :style="{ width: '100%', height: '100%' }"
              :src="item.poster"
            ></image>
            <view v-else-if="item.type === 'video' && !item.poster" class="video-box">
              <mxp-icon name="bofang" color="#ffffff" size="28px"></mxp-icon>
            </view>
          </view>
          <!-- 删除按钮 -->
          <view v-if="item.uploadState !== 'uploading'" class="item-box-del-box" @click.stop.prevent="delFile(item, index)">
            <mxp-icon name="guanbi" color="#ffffff" size="12px"></mxp-icon>
          </view>
          <!-- 上传中 -->
          <view v-if="item.uploadState === 'uploading'" key="view-2" class="item-box-uploading-box">
            <mxp-icon name="jiazai" color="#ffffff" :spin="true" size="18px"></mxp-icon>
            <text style="margin-top: 6px; font-size: 12px; color: #ffffff">上传中</text>
          </view>
          <!-- 上传失败点击重试 -->
          <view v-if="item.uploadState === 'failed'" key="view-3" class="item-box-failed-box" @click.stop.prevent="reUpload(item, index)">
            <mxp-icon name="shibai" color="#ffffff" size="18px"></mxp-icon>
            <text style="margin-top: 6px; font-size: 12px; color: #ffffff">点击重试</text>
          </view>
        </view>
      </mxp-grid-item>
      <mxp-grid-item v-if="showAddButt" :key="fileList.length" :index="fileList.length" @click="chooseFile">
        <slot name="addButton">
          <view class="item-add-box item-border-radius" :style="[newAddButtonStyle, addButtonStyle]">
            <mxp-icon name="jiahao" :color="addColor" size="24px"></mxp-icon>
            <text style="margin-top: 6px; font-size: 12px" :style="{ color: addColor }">点击上传</text>
          </view>
        </slot>
      </mxp-grid-item>
    </mxp-grid>
    <!-- 视频预览 -->
    <mxp-video-preview-popup v-if="maxVideo > 0" ref="mxpVideoPreviewPopupRef"></mxp-video-preview-popup>
  </view>
</template>

<script lang="ts" setup>
  import { type CSSProperties, computed, nextTick, ref, watch } from 'vue';
  import MxpGrid from '../mxp-grid/mxp-grid.vue';
  import MxpGridItem from '../mxp-grid-item/mxp-grid-item.vue';
  import MxpVideoPreviewPopup from './components/mxp-video-preview-popup.vue';

  import type { $AllColorStr, $AllSizeUnitStr, $SizeUnitStr } from '@/mxp-ui/mxp-tool/types/interface';
  import MxpIcon from '../mxp-icon/mxp-icon.vue';
  import { removeObjectEmpty } from '../mxp-tool/utils/obj';
  import { myChooseImage } from '../mxp-tool/utils/base-api';

  export type $UploadFileOption = {
    /** 开发者服务器 url */
    url?: string;
    /** 文件类型，image/video/audio，仅支付宝小程序，且必填。 */
    fileType?: 'audio' | 'image' | 'video';
    /** 要上传的文件对象 */
    file?: File | UniApp.ChooseImageSuccessCallbackResultFile;
    /** 要上传文件资源的路径 */
    filePath?: string;
    /** 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容  */
    name?: string;
    /** HTTP 请求 Header, header 中不能设置 Referer */
    header?: Record<number | string, unknown>;
    /** HTTP 请求中其他额外的 form data */
    formData?: Record<number | string, unknown>;
    /** 超时时间，单位 ms */
    timeout?: number;
  };

  export type $ResultItem = {
    /** 选定视频的时间长度 */
    duration?: number;
    /** 选定视频的数据量大小 */
    size?: number;
    /** 返回选定视频的长 */
    height?: number;
    /** 返回选定视频的宽 */
    width?: number;
    /** 包含扩展名的文件名称，仅H5支持 */
    name?: string;
    /** 视频海报图片 */
    poster?: string;
    /** 文件 */
    file?: File | UniApp.ChooseImageSuccessCallbackResultFile;
    /** 上传状态 */
    uploadState: 'failed' | 'success' | 'uploading';
    /** 类型 */
    type: 'image' | 'video';
    /** 路径 */
    path: string;
  };
  export type $ChangeEmits = {
    /** 文件路径 */
    path: string;
    /** 文件对象 */
    file?: File | UniApp.ChooseImageSuccessCallbackResultFile;
    /** 上传请求的返回的内容 */
    uploadResult: any;
    /** 上传状态 */
    uploadState: 'failed' | 'success';
  };

  export type $AfterUploadCallbackParam = Omit<$ChangeEmits, 'uploadState'>;

  export type $UploadMediaProps = {
    /** 没排显示几张图片 */
    col?: number;
    /** 图片 的 左右间距 */
    xSpace?: $SizeUnitStr;
    /** item 的 上下间距 */
    ySpace?: $SizeUnitStr;
    /** item的高度， 除了支持传入具体的尺寸外，还支持传入square代表正方形（即高度和宽度一致），默认为 square */
    itemHeight?: $AllSizeUnitStr | 'square';
    /** 添加按钮的颜色 */
    addColor?: $AllColorStr;
    /** 添加按钮的背景颜色 */
    addBackground?: $AllColorStr;
    /** 添加按钮的颜色 */
    addButtonStyle?: CSSProperties;
    /** 最多选择几张图片 */
    maxImage?: number;
    /** 最多选择几个视频 */
    maxVideo?: number;
    /** 拍摄视频最长拍摄时间，单位秒。时间范围为 3s 至 60s 之间。注意从相册中选取的视频不受此限制 */
    videoMaxDuration?: number;
    /** #是否禁用 */
    disabled?: boolean;
    /** 默认的图片链接 */
    modelValue?: $ResultItem | $ResultItem[];
    /** #选择图片的大小 original 原图，compressed 压缩图，默认二者都有 ( H5传值无效) */
    sizeType?: ('compressed' | 'original')[];
    /** #选取视频的方式，相册和拍摄 */
    sourceType?: ('album' | 'camera')[];
    /** 使用前置或后置摄像头, back使用后置摄像头, front使用前置摄像头 */
    camera?: 'back' | 'front';
    /** uni.uploadFile() 的配置信息，如header，formData，timeout等 */
    uploadFileOption?: $UploadFileOption;
    /** 上传的 url */
    url?: string;
    /** 上传成功的响应码，默认200 */
    statusCode?: number;
    /** 自定义上传的方法 */
    curUploadCallback?: (fileObj: $ResultItem, index?: number) => Promise<any>;
    /** 上传之后的处理函数,需要返回 true 或 false 支持 Promise ,注意：当传入curUploadCallback时，此参数无效 */
    afterUploadCallback?: (fileObj: $AfterUploadCallbackParam) => Promise<boolean> | boolean;
  };

  defineOptions({
    name: 'mxp-upload-media',
  });
  const props = withDefaults(defineProps<$UploadMediaProps>(), {
    col: 4,
    xSpace: '10px',
    ySpace: '10px',
    itemHeight: 'square',
    addColor: '#d9d9d9',
    addButtonStyle: () => ({}),
    maxImage: 1,
    maxVideo: 0,
    disabled: false,
    modelValue: () => [],
    sizeType: () => ['compressed'],
    sourceType: () => ['album', 'camera'],
    formData: () => ({}),
    url: '',
    uploadFileOption: () => ({}),
    curUploadCallback: undefined,
    afterUploadCallback: undefined,
    statusCode: 200,
    addBackground: 'transparent',
    camera: 'back',
    videoMaxDuration: 60,
  });
  defineSlots<{
    /** 自定义添加按钮 */
    addButton: () => void;
  }>();
  const emits = defineEmits<{
    (e: 'update:modelValue', vul: any): void;
    (e: 'change', vul: $ChangeEmits): void;
    (e: 'delete', vul: $ResultItem & { index: number }): void;
  }>();

  const mxpVideoPreviewPopupRef = ref<InstanceType<typeof MxpVideoPreviewPopup> | null>(null);
  const fileList = ref<$ResultItem[]>([]);
  /** 已选择的图片的数量 */
  const imageListLength = computed<number>(() => fileList.value.filter((item) => item.type === 'image').length);
  /** 已选择的视频的数量 */
  const videoListLength = computed<number>(() => fileList.value.filter((item) => item.type === 'video').length);
  /** 是否显示添加按钮 */
  const showAddButt = computed<boolean>(() => props.maxImage > imageListLength.value || props.maxVideo > videoListLength.value);
  /** 添加按钮的颜色 */
  const newAddButtonStyle = computed<CSSProperties>(() => {
    return { background: props.addBackground, border: `1px dashed ${props.addColor}`, ...props.addButtonStyle };
  });

  watch(
    () => props.modelValue,
    (val) => {
      const list = Array.isArray(val) ? val : val ? [val] : [];
      fileList.value = list;
    },
    { immediate: true }
  );

  /** 选择图片或者视频 */
  function chooseFile() {
    if (!props.disabled) {
      const maxImageNumb = props.maxImage - imageListLength.value;
      const maxVideoNumb = props.maxVideo - videoListLength.value;
      console.log(maxImageNumb, maxVideoNumb);
      if (maxImageNumb > 0 && maxVideoNumb > 0) {
        uni.showActionSheet({
          itemList: ['图片', '视频'],
          success(res) {
            res.tapIndex === 0 ? imgAdd() : videoAdd();
          },
        });
      } else if (maxImageNumb > 0) {
        imgAdd();
      } else if (maxVideoNumb > 0) {
        videoAdd();
      }
    }
  }

  /** 选择图片 */
  async function imgAdd() {
    const maxNumb = props.maxImage - imageListLength.value;
    if (!props.disabled && maxNumb > 0) {
      const res = await myChooseImage({
        count: maxNumb,
        sizeType: props.sizeType, // 可以指定是原图还是压缩图，默认二者都有
        sourceType: props.sourceType,
      });
      const files = Array.isArray(res.tempFiles) ? res.tempFiles : [res.tempFiles];
      const paths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
      const list: $ResultItem[] = [];
      paths.forEach((path, index) => {
        list.push({ path, file: files[index]!, type: 'image', uploadState: 'uploading' });
      });
      uploadFiles(list);
    }
  }

  /** 选择视频 */
  function videoAdd() {
    const maxNumb = props.maxVideo - videoListLength.value;
    if (!props.disabled && maxNumb > 0) {
      uni.chooseVideo({
        sizeType: props.sizeType,
        sourceType: props.sourceType,
        maxDuration: props.videoMaxDuration,
        camera: props.camera,
        success: (res) => {
          uploadFiles([
            {
              path: res.tempFilePath,
              file: res.tempFile,
              duration: res.duration,
              size: res.size,
              height: res.height,
              width: res.width,
              name: res.name,
              type: 'video',
              uploadState: 'uploading',
            },
          ]);
        },
      });
    }
  }

  /** 删除 */
  function delFile(item: $ResultItem, index: number) {
    fileList.value.splice(index, 1);
    emits('delete', { ...item, index });
    setEmit();
  }

  /** 图片预览 */
  function preview(index: number) {
    const item = fileList.value[index]!;
    if (item.type === 'image') {
      uni.previewImage({
        urls: fileList.value.map((_item) => _item.path),
        current: index,
        indicator: 'number',
        loop: true,
      });
    } else if (item.type === 'video') {
      mxpVideoPreviewPopupRef.value?.show({ url: item.path, poster: item.poster });
    }
  }

  /** 重新上传 */
  async function reUpload(fileObj: $ResultItem, index: number) {
    try {
      fileObj.uploadState = 'uploading';
      await uploadSingle(fileObj, index);
    } catch (e) {
      console.log('重新上传失败', e);
    }
  }

  /** 上传单个图片 */
  async function uploadSingle(fileObj: $ResultItem, _index: number) {
    let index = _index;
    if (_index < 0) {
      // 小于 0 说明是新增，大于等于 0 说明是重新上传
      fileList.value.push(fileObj);
      index = fileList.value.length - 1;
    }

    try {
      let res = undefined;
      if (typeof props.curUploadCallback === 'function') {
        res = await props.curUploadCallback(fileObj, index);
        fileList.value[index]!.uploadState = 'success';
        emits('change', { file: fileObj.file, path: fileObj.path, uploadResult: res, uploadState: 'success' });
      } else {
        const op: UniApp.UploadFileOption = {
          ...removeObjectEmpty(props.uploadFileOption || {}),
          filePath: fileObj.path,
          fileType: fileObj.type,
          url: props.url || props.uploadFileOption?.url || '',
        };
        res = await uni.uploadFile(op);
        if (res && res.statusCode === props.statusCode) {
          let data = res.data;
          try {
            // 尝试解析 json
            data = JSON.parse(res.data);
          } catch (e) {}

          let isSuccess = true;
          if (typeof props.afterUploadCallback === 'function') {
            isSuccess = await props.afterUploadCallback({ file: fileObj.file, path: fileObj.path, uploadResult: data });
          }
          if (isSuccess) {
            fileList.value[index]!.uploadState = 'success';

            emits('change', { file: fileObj.file, path: fileObj.path, uploadResult: data, uploadState: 'success' });
            return Promise.resolve(data);
          }
        }
      }
      fileList.value[index]!.uploadState = 'failed';
      emits('change', { file: fileObj.file, path: fileObj.path, uploadResult: res, uploadState: 'failed' });
      return Promise.reject(res);
    } catch (error) {
      console.log('上传失败', error);
      fileList.value[index]!.uploadState = 'failed';
      emits('change', { file: fileObj.file, path: fileObj.path, uploadResult: error, uploadState: 'failed' });
      return Promise.reject(error);
    }
  }

  /** 批量上传文件 */
  async function uploadFiles(files: $ResultItem[]) {
    try {
      const list = files.map((item) => uploadSingle(item, -1));
      await Promise.all(list);
      setEmit();
    } catch (error) {
      setEmit();
    }
  }

  /** 发送事件 */
  function setEmit() {
    nextTick(() => {
      if (props.maxImage > 1) {
        emits('update:modelValue', fileList.value);
      } else {
        emits('update:modelValue', fileList.value[0] || '');
      }
    });
  }
</script>

<style lang="scss" scoped>
  .mxp-upload-media {
    display: flex;
    flex: 1;
    flex-flow: row wrap;
    align-items: center;
    justify-content: flex-start;
    width: 100%;
  }

  .item-border-radius {
    overflow: hidden;
    border-radius: 6px;
  }

  .item-image-box {
    display: flex;
    flex-flow: row wrap;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    overflow: hidden;

    .video-box {
      display: flex;
      flex-flow: row wrap;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 100%;
      overflow: hidden;
      background: #000000;
    }
  }

  .item-box {
    position: relative;
    margin-right: 30rpx;
    margin-bottom: 30rpx;
    overflow: hidden;
    border-radius: 6px;
  }

  .item-box-del-box {
    position: absolute;
    top: -8px;
    right: -8px;
    z-index: 3;
    display: flex;
    flex-flow: row wrap;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding-top: 6px;
    padding-right: 6px;
    background: #ff0000;
    border-radius: 50%;
  }

  .item-box-uploading-box {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.3);
  }

  .item-box-failed-box {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.3);
  }

  .item-add-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    border: 1px dashed #d9d9d9;
  }
</style>
